此文为Hexo Stellar主题用户提供侧边栏热门与热评文章展示教程。先部署Artalk评论系统,再在指定目录创建widgets.yml文件,添加含样式与脚本的组件代码。通过修改apiBaseUrl、siteName等变量配置,实现侧边栏切换展示热门和热评文章,还含数字标识样式优化,助力提升网站交互体验。

一、部署Artalk评论系统

  • Artalk官方文档
    博主使用的是Docker-Compose文件部署,一键部署运行。

二、增加主题侧边栏组件

1. 创建文件widgets.yml

  • 在 /blog/source/_data/ 文件夹下,创建 widgets.yml 文件

2. 创建热门/热评文章组件

  1. /blog/source/_data/widgets.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
most_comment_pv_pages:
layout: markdown
content: |
<link rel="stylesheet" href="/path/to/your/tab-widget.css">

<div id="tab-widget-artalk">
<div class="widget-header dis-select">
<span class="tab-title" data-tab-title="popular" style="display: inline;">热门文章</span>
<span class="tab-title" data-tab-title="commented" style="display: none;">热评文章</span>
<div class="cap-action">
<div class="icon-button active" data-tab="popular" title="热门文章">
<svg t="1754474761360" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5468" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M388.266667 512l149.333333 149.333333 59.733333-59.733333-89.6-89.6L597.333333 422.4l-59.733333-59.733333L388.266667 512zM853.333333 512c0-187.733333-153.6-341.333333-341.333333-341.333333s-341.333333 153.6-341.333333 341.333333 153.6 341.333333 341.333333 341.333333 341.333333-153.6 341.333333-341.333333z m-85.333333 0c0 140.8-115.2 256-256 256s-256-115.2-256-256 115.2-256 256-256 256 115.2 256 256z" fill="#444444" p-id="5469"></path></svg>
</div>
<div class="icon-button" data-tab="commented" title="热评文章">
<svg t="1754474765479" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5615" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M635.733333 512l-149.333333-149.333333L426.666667 422.4l89.6 89.6-89.6 89.6 59.733333 59.733333 149.333333-149.333333zM170.666667 512c0 187.733333 153.6 341.333333 341.333333 341.333333s341.333333-153.6 341.333333-341.333333-153.6-341.333333-341.333333-341.333333-341.333333 153.6-341.333333 341.333333z m85.333333 0c0-140.8 115.2-256 256-256s256 115.2 256 256-115.2 256-256 256-256-115.2-256-256z" fill="#444444" p-id="5616"></path></svg>
</div>
</div>
</div>

<div class="tab-content active" id="tab-popular" style="margin-top: 0px;">
<div class="loading">加载中...</div>
</div>
<div class="tab-content" id="tab-commented" style="margin-top: 0px;">
<div class="loading">加载中...</div>
</div>
</div>

<script src="/path/to/your/tab-widget.js"></script>
  1. 对应的CSS样式代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#tab-widget-artalk {
border-radius: 12px;
color: #000;
font-size: calc(var(--fsp) * 0.85);
line-height: 1.4;
font-family: "LXGW WenKai Screen", system-ui, "Microsoft Yahei", "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizelegibility;
-webkit-tap-highlight-color: transparent;
word-break: break-all;
text-align: justify;
outline: 0;
user-select: none;
}

.widget-body:has(#tab-widget-artalk) {
background: transparent !important;
}

.widget-header.dis-select {
font-family: "LXGW WenKai Screen", system-ui, "Microsoft Yahei", "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizelegibility;
-webkit-tap-highlight-color: transparent;
word-break: break-all;
text-align: justify;
visibility: visible;
outline: 0;
user-select: none;
display: flex;
justify-content: space-between;
align-items: baseline;
line-height: 28px;
font-weight: 500;
font-size: .8125rem;
color: var(--text-p1);
}

#tab-widget-artalk .widget-header.dis-select {
padding-left: 0px !important;
padding-right: 0px !important;
}

.widget-header .tab-title {
font-size: inherit;
font-weight: inherit;
color: var(--text-p1);
margin: 0;
}

.widget-header .cap-action {
display: flex;
gap: 12px;
}

.widget-header .icon-button {
cursor: pointer;
color: var(--text-p2);
padding: 4px;
border-radius: 6px;
outline: 0;
}

.widget-header .icon-button svg {
width: 15.6px;
height: 15.6px;
stroke: currentColor;
stroke-width: 1.5;
fill: none;
}

.tab-content {
display: none;
padding: 0;
}

.tab-content.active {
display: block;
}

.article-item {
margin-bottom: 4px;
padding: 4px 10px 4px 22px;
border-radius: 8px;
color: #000;
position: relative;
font-size: 0.95em;
display: flex;
align-items: center;
height: 32px;
font-size: 12px;
}

.tab-content .article-item:last-child {
margin-bottom: 8px;
}

.article-item a {
color: #000;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
padding-left: 10px;
font-weight: 500;
flex: 1;
}

.article-item .meta {
display: none;
}

badge.img-badge.left.hot.em12 {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 18px;
height: 18px;
padding: 0;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, .1);
z-index: 1;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
}

badge.img-badge.left.hot.em12 svg {
width: 12px;
height: 12px;
}

badge.img-badge.left.hot.em12.top1 { background: #FF3B30; }
badge.img-badge.left.hot.em12.top2 { background: #FF958C; }
badge.img-badge.left.hot.em12.top3 { background: #FF9500; }
badge.img-badge.left.hot.em12.top4,
badge.img-badge.left.hot.em12.top5,
badge.img-badge.left.hot.em12.top6,
badge.img-badge.left.hot.em12.top7,
badge.img-badge.left.hot.em12.top8,
badge.img-badge.left.hot.em12.top9,
badge.img-badge.left.hot.em12.top10 { background: #8E8E93; }

.loading {
text-align: center;
padding: 20px;
color: #555;
}

#tab-widget-artalk {
--bg-a100: transparent !important;
}

.icon-button,
.icon-button:hover,
.icon-button:focus,
.icon-button:active {
background-color: transparent !important;
background: transparent !important;
box-shadow: none !important;
border: none !important;
outline: none !important;
-webkit-tap-highlight-color: transparent !important;
}
  1. 对应的JS代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
const config = {
apiBaseUrl: "",
siteName: "",
popularLimit: ,
commentedLimit: ,
author: ""
};

const popularApiUrl = `${config.apiBaseUrl}/api/v2/stats/pv_most_pages?site_name=${config.siteName}&limit=${config.popularLimit}`;
const commentedApiUrl = `${config.apiBaseUrl}/api/v2/stats/comment_most_pages?site_name=${config.siteName}&limit=${config.commentedLimit}`;

function processTitle(title) {
if (!title) return '无标题文章';
const authorSuffixPattern = new RegExp(`\\s*-\\s*${config.author}\\s*$`, 'i');
return title.replace(authorSuffixPattern, '').trim() || title.trim();
}

// 生成数字1-10的圆角正方形SVG图标(数字占比80%)
function getNumberSvg(number) {
const svgs = {
1: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">1</text></svg>`,
2: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">2</text></svg>`,
3: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">3</text></svg>`,
4: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">4</text></svg>`,
5: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">5</text></svg>`,
6: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">6</text></svg>`,
7: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">7</text></svg>`,
8: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">8</text></svg>`,
9: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="75" font-family="Arial" font-size="70" font-weight="bold" text-anchor="middle" fill="#FFFFFF">9</text></svg>`,
10: `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="15" ry="15" fill="none"/><text x="50" y="78" font-family="Arial" font-size="60" font-weight="bold" text-anchor="middle" fill="#FFFFFF">10</text></svg>`
};
return svgs[number] || svgs[1];
}

function loadPopularArticles() {
const container = document.getElementById('tab-popular');
fetch(popularApiUrl)
.then(res => res.json())
.then(data => {
container.innerHTML = '';
(data.data || []).forEach((article, index) => {
const title = processTitle(article.title);
const item = document.createElement('div');
item.className = 'article-item';
item.innerHTML = `<badge class="img-badge left hot em12 top${index + 1}">${getNumberSvg(index + 1)}</badge><a href="${article.url}" title="${title}" >${title}</a>`;
container.appendChild(item);
});
})
.catch(() => container.innerHTML = '<div class="loading">热门文章加载失败</div>');
}

function loadCommentedArticles() {
const container = document.getElementById('tab-commented');
fetch(commentedApiUrl)
.then(res => res.json())
.then(data => {
container.innerHTML = '';
(data.data || []).forEach((article, index) => {
const title = processTitle(article.title);
const item = document.createElement('div');
item.className = 'article-item';
item.innerHTML = `<badge class="img-badge left hot em12 top${index + 1}">${getNumberSvg(index + 1)}</badge><a href="${article.url}" title="${title}" >${title}</a>`;
container.appendChild(item);
});
})
.catch(() => container.innerHTML = '<div class="loading">热评文章加载失败</div>');
}

document.querySelectorAll('.widget-header .icon-button').forEach(button => {
button.addEventListener('click', () => {
const tab = button.dataset.tab;
if (!tab) return;
document.querySelectorAll('.widget-header .icon-button')
.forEach(btn => btn.classList.toggle('active', btn === button));
document.querySelectorAll('#tab-widget-artalk .tab-content')
.forEach(content => content.classList.toggle('active', content.id === `tab-${tab}`));
document.querySelectorAll('.widget-header .tab-title')
.forEach(title => {
title.style.display = (title.dataset.tabTitle === tab) ? 'inline' : 'none';
});
if (tab === 'commented' && document.getElementById('tab-commented').innerHTML.includes('加载中')) {
loadCommentedArticles();
}
});
});

// 初始化加载热门文章
loadPopularArticles();

3. 修改配置变量

  • 找到这三个变量名 apiBaseUrl、siteName、popularLimit、commentedLimit、author 分别是Artalk部署地址、网站名称、展示热门文章数、展示热评文章数、站点标题/站点副标题。
  • 复制代码时注意代码缩进,不正确的缩进可能会使代码不生效。